Nossa proposta consiste em implementar uma ferramenta automática para identificação de trÃades de formação de acordes, e consequentemente identificar os acordes que compõem gravações de instrumentos. Convencionalmente, tal identificação é feita através da escuta de trechos da gravação, por músicos treinados, que escrevem manualmente as cifras à medida que são identificadas.
A implementação consiste em uma análise do módulo do espectro de frequências da gravação em relação com o tempo, de modo a obter em intervalos de tempo fixos as notas predominantes e as trÃades formadas por tais notas. As perguntas que motivam este trabalho são: qual grau de assertividade que uma análise nessas condições pode promover? Esse grau de assertividade auxiliaria um músico ou uma musicista a escrever cifras de determinada gravação?
Em processamento digital de áudio, a análise no domÃnio da frequência, principalmente o de amplitude, pode trazer bastante informação sobre o sinal, mais do que a simples análise temporal.
Fs = 44100
Tmax = 2
A3 = 440.0
E3 = 329.63
t = np.linspace(0, Tmax, Tmax*Fs)
s = 0.7*np.sin(2*np.pi*A3*t) + 0.3*np.sin(2*np.pi*E3*t)
ipd.Audio(s, rate=Fs)
plt.figure(figsize=(15,5))
plt.plot(t, s)
plt.xlabel('Tempo (s)')
plt.ylabel('Intensidade')
plt.xlim(0.0, 0.025)
(0.0, 0.025)
S = fftpack.fft(s)
S = S/len(S)
f = np.linspace(-Fs/2, Fs/2, len(S))
Splot = np.abs(fftpack.fftshift(S))
plt.figure(figsize=(15,5))
plt.plot(f, Splot)
plt.xlabel('Frequência (Hz)')
plt.ylabel('Intensidade')
plt.xlim(300, 500)
(300, 500)
É possÃvel dividir as amostras de um sinal de áudio em janelas de tempo, de modo que possamos determinar o espectro periódico do sinal.
tchirp = np.linspace(0, 3, 3*Fs)
s = signal.chirp(tchirp, 200.0, 3, 1000.0, 'logarithmic')
ipd.Audio(s, rate=Fs)
f, t, Stf = signal.stft(s, fs=Fs, nperseg=2048)
plt.figure(figsize=(15,5))
Stfplot = np.abs(Stf)
plt.pcolormesh(t, f, Stfplot)
plt.ylabel('Frequência (Hz)')
plt.xlabel('Tempo (s)')
plt.ylim(0, 1000)
(0, 1000)
Note que, por conta da FFT aplicada em uma janela de aproximadamente 50ms, há uma imprecisão na medida de frequência vista por uma linha espessa no gráfico. Ao aumentar o tamanho dessa janela, obtemos maior precisão da frequência, mas perdemos informação temporal.
tsig = np.linspace(0, 1, Fs)
tsil = np.linspace(0, 0.5, int(0.5*Fs))
# 2 segundos de 200Hz, silencio e 2 segundos de 300Hz
s1 = np.sin(2*np.pi*200.0*tsig)
silent = 1e-7*tsil
s2 = np.sin(2*np.pi*300.0*tsig)
s = np.concatenate((s1, silent, s2))
f, t, Stf = signal.stft(s, fs=Fs, nperseg=2048)
plt.figure(figsize=(15,5))
Stfplot = np.abs(Stf)
plt.pcolormesh(t, f, Stfplot)
plt.ylabel('Frequência (Hz)')
plt.xlabel('Tempo (s)')
plt.ylim(0, 600)
(0, 600)
A escala cromática contém 12 notas com intervalos de semitons. Como duas oitavas consecutivas possuem a relação de dobro/metade da frequência, define-se a relação de frequência entre semitons:
$$ {ST}_{n} = 2^{1/12} \cdot {ST}_{n-1} = 2^{n/12} \cdot {ST}_{0} $$Sendo $${ST}_{0} = 55Hz$$ o Lá Zero (A0).
Podemos entender essa relação simétrica entre semitons desenhada por um cÃrculo.

Figura 1 - Escala cromática vista como um cÃrculo (Autor: David Eppstein)
Em termos de frequência, podemos associar as notas a pontos de uma curva helicoidal, cuja projeção destes pontos obtém o cÃrculo da figura 1, e a distância entre pontos da mesma projeção (o passo da hélice) cresce o dobro em relação à anterior.

Figura 2 - Escala cromática vista como uma helicoidal
Vale ressaltar que muitos sistemas de afinações não usam esta escala simétrica de semitons.
Passos seguidos:
Fs, data = wavfile.read('../wav/ChromaticScaleUp.wav')
ipd.Audio(data, rate=Fs)
f, t, Stf = signal.stft(data, fs=Fs, nperseg=2048)
plt.figure(figsize=(15,5))
Stfplot = np.abs(Stf)
plt.pcolormesh(t, f, Stfplot)
plt.ylabel('Frequência (Hz)')
plt.xlabel('Tempo (s)')
plt.ylim(0, 2000)
(0, 2000)
scale, t, Ch = chromagram_stft(data, rate=Fs)
plt.figure(figsize=(15,5))
chromaplot(t, scale, Ch)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
Separamos as notas em trÃades e decidimos o acorde maior, menor, diminuto ou aumentado
sample = 'C-A'
Fs, data = wavfile.read('../wav/{}.wav'.format(sample), 44100)
ipd.Audio(data, rate=Fs)
f, t, Stf = signal.stft(data, fs=Fs, nperseg=2048)
plt.figure(figsize=(15,5))
Stfplot = np.abs(Stf)
plt.pcolormesh(t, f, Stfplot)
plt.ylabel('Frequência (Hz)')
plt.xlabel('Tempo (s)')
plt.ylim(20, 2000)
(20, 2000)
scale, t, Ch = chromagram_stft(data, rate=Fs)
plt.figure(figsize=(15,5))
chromaplot(t, scale, Ch)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
note_groups = classifier.get_note_list(data, rate=Fs)
# Plotting results
plottable_groups = np.zeros(shape=(12, len(note_groups)))
scale = [s for s in scale]
for i in range(len(note_groups)):
for note in note_groups[i]:
plottable_groups[scale.index(note), i] = 1
plt.figure(figsize=(15,5))
chromaplot(t, scale, plottable_groups)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
plt.figure(figsize=(1,1))
chromaplot([0,1], ['Major', 'Minor', 'Diminished', 'Augmented', ''], [[4], [3], [2], [1], [0]])
plt.figure(figsize=(15,5))
chromaplot(np.linspace(t[0], t[-1], num=int(len(t)/window)), scale, plottable_chords)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
from precision_checker import print_precisions
print_precisions(sample)
The exact precision is 55.56% The near precision is 55.56%
sample = 'Reggae1'
Fs, data = wavfile.read('../wav/{}.wav'.format(sample), 44100)
scale, t, Ch = chromagram_stft(data, rate=Fs)
ipd.Audio(data, rate=Fs)
note_groups = classifier.get_note_list(data, rate=Fs)
# Plotting results
plottable_groups = np.zeros(shape=(12, len(note_groups)))
scale = [s for s in scale]
for i in range(len(note_groups)):
for note in note_groups[i]:
plottable_groups[scale.index(note), i] = 1
plt.figure(figsize=(15,5))
chromaplot(t, scale, plottable_groups)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
plt.figure(figsize=(1,1))
chromaplot([0,1], ['Major', 'Minor', 'Diminished', 'Augmented', ''], [[4], [3], [2], [1], [0]])
plt.figure(figsize=(15,5))
chromaplot(np.linspace(t[0], t[-1], num=int(len(t)/window)), scale, plottable_chords)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
print_precisions(sample)
The exact precision is 49.32% The near precision is 53.88%
sample = 'Rock1'
Fs, data = wavfile.read('../wav/{}.wav'.format(sample), 44100)
scale, t, Ch = chromagram_stft(data, rate=Fs)
ipd.Audio(data, rate=Fs)
note_groups = classifier.get_note_list(data, rate=Fs)
# Plotting results
plottable_groups = np.zeros(shape=(12, len(note_groups)))
scale = [s for s in scale]
for i in range(len(note_groups)):
for note in note_groups[i]:
plottable_groups[scale.index(note), i] = 1
plt.figure(figsize=(15,5))
chromaplot(t, scale, plottable_groups)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
plt.figure(figsize=(1,1))
chromaplot([0,1], ['Major', 'Minor', 'Diminished', 'Augmented', ''], [[4], [3], [2], [1], [0]])
plt.figure(figsize=(15,5))
chromaplot(np.linspace(t[0], t[-1], num=int(len(t)/window)), scale, plottable_chords)
plt.xlabel('Tempo (s)')
Text(0.5, 0, 'Tempo (s)')
print_precisions(sample)
The exact precision is 30.36% The near precision is 31.55%